//
//  electra.c
//  electra1131
//
//  Created by CoolStar on 6/6/18.
//  Copyright © 2018 CoolStar. All rights reserved.
//

#include "electra.h"
#include "kmem.h"
#include "kutils.h"
#include "patchfinder64.h"
#include "kexecute.h"
#include "codesign.h"
#include "offsetof.h"
#include "remap_tfp_set_hsp.h"
#include "unlocknvram.h"
#include "apfs_util.h"
#include "rootfs_remount.h"
#include "electra_objc.h"
#include "bootstrap.h"
#include "file_utils.h"
#include "utils.h"
#include "nonce.h"
#include "unliberios.h"
#include "removePotentialJailbreak.h"
#include "detectConflictJailbreak.h"
#include "debug.h"

mach_port_t tfpzero = MACH_PORT_NULL;

bool acknowledgeSnapshotWarning = false;

void snapshotWarningRead(void){
    acknowledgeSnapshotWarning = true;
}

int start_electra(mach_port_t tfp0, bool enable_tweaks){   
    printf("Starting Electra...\n");
    
    int err = ERR_NOERR;
    
    if (tfp0 == MACH_PORT_NULL) {
        return ERR_TFP0;
    }
    
    tfpzero = tfp0;
    
    uint64_t kernel_base = find_kernel_base();
    
    // Loads the kernel into the patch finder, which just fetches the kernel memory for patchfinder use
    init_kernel(kernel_base, NULL);
    
    // Get the slide
    uint64_t slide = kernel_base - 0xFFFFFFF007004000;
    printf("slide: 0x%016llx\n", slide);
    
    init_kexecute();
    
    // Get our and the kernels struct proc from allproc
    uint32_t our_pid = getpid();
    uint64_t our_proc = get_proc_struct_for_pid(our_pid);
    uint64_t launchd_proc = get_proc_struct_for_pid(1);
    uint64_t kern_proc = get_proc_struct_for_pid(0);
    uint32_t amfid_pid = find_pid_of_proc("amfid");
    uint64_t amfid_proc = get_proc_struct_for_pid(amfid_pid);
    uint32_t cfprefsd_pid = find_pid_of_proc("cfprefsd");
    
    if (!our_proc || !launchd_proc || !kern_proc || !amfid_proc) {
        err = ERR_JAILBREAK;
        goto out;
    }
    
    printf("our proc is at 0x%016llx\n", our_proc);
    printf("launchd proc is at 0x%016llx\n", launchd_proc);
    printf("kern proc is at 0x%016llx\n", kern_proc);
    printf("amfid proc is at 0x%016llx\n", amfid_proc);
    
    uint32_t csflags;
    
    csflags = rk32(our_proc + offsetof_p_csflags);
    csflags = (csflags | CS_PLATFORM_BINARY | CS_INSTALLER | CS_GET_TASK_ALLOW) & ~(CS_RESTRICT | CS_HARD | CS_KILL);
    wk32(our_proc + offsetof_p_csflags, csflags);
    
    set_csblob(our_proc);
    
    csflags = rk32(launchd_proc + offsetof_p_csflags);
    wk32(launchd_proc + offsetof_p_csflags, (csflags | CS_PLATFORM_BINARY | CS_INSTALLER | CS_GET_TASK_ALLOW) & ~(CS_RESTRICT | CS_HARD));
    
    csflags = rk32(amfid_proc + offsetof_p_csflags);
    wk32(amfid_proc + offsetof_p_csflags, (csflags | CS_PLATFORM_BINARY | CS_INSTALLER | CS_GET_TASK_ALLOW) & ~(CS_RESTRICT | CS_HARD));
    
    if (find_pid_of_proc("jailbreakd")) {
        err = ERR_ALREADY_JAILBROKEN;
        goto out;
    }
    
    // Properly copy the kernel's credentials so setuid(0) doesn't crash
    
    uint64_t copyout = find_copyout();
    uint64_t bcopy = find_bcopy();
    uint64_t bzero = find_bzero();
    
    uint64_t kern_ucred = 0;
    kexecute(copyout, kern_proc+offsetof_p_ucred, (uint64_t)&kern_ucred, sizeof(kern_ucred), 0, 0, 0, 0);
    
    uint64_t self_ucred = 0;
    kexecute(copyout, our_proc+offsetof_p_ucred, (uint64_t)&self_ucred, sizeof(self_ucred), 0, 0, 0, 0);
    
    kexecute(bcopy, kern_ucred + 0x78, self_ucred + 0x78, sizeof(uint64_t), 0, 0, 0, 0);
    kexecute(bzero, self_ucred + 0x18, 12, 0, 0, 0, 0, 0);
    
    setuid(0);
    
    if (getuid()) {
        err = ERR_JAILBREAK;
        goto out;
    }
    
    printf("our uid is %d\n", getuid());
    
    // Test writing to file
    {
        FILE *f = fopen("/var/mobile/test.txt", "w");
        if (f == 0) {
            printf("failed to write test file");
        } else {
            printf("wrote test file: %p\n", f);
        }
        
        unlink("/var/mobile/test.txt");
    }
    
    remap_tfp0_set_hsp4(&tfpzero);
    
    unlocknvram();
    
    printf("Mounted volumes:\n");
    printf("\n");
    
    int forkAndRunMount = fork();
    if (forkAndRunMount > 0){
        waitpid(forkAndRunMount, NULL, 0);
    } else if (forkAndRunMount == 0){
        const char *args_mount[] = {"mount", NULL};
        execv("/sbin/mount", (char **)&args_mount);
        printf("Child should never reach here!\n");
        exit(-1);
    } else {
        printf("Unable to fork!\n");
    }
    
    int ret;
    
    // Only set a generator if one is not already set or if the user has specified one
    const char *currentgen = getgen();
    if (!currentgen || userGenerator()) {
        const char *gen = genToSet();
        // Only actually set the generator if it is not already set
        if (!currentgen || strcasecmp(currentgen, gen) != 0) {
            printf("Setting generator to %s\n", gen);
            ret = setgen(gen);
            printf("ret: %d\n", ret);
        } else {
            printf("Requested generator already set: %s\n", gen);
        }
    }
    
    printf("nvram variables:\n");
    printf("\n");
    
    int forkAndRunNVRAM = fork();
    if (forkAndRunNVRAM > 0){
        waitpid(forkAndRunNVRAM, NULL, 0);
    } else if (forkAndRunNVRAM == 0){
        const char *args_nvram[] = {"nvram", "-p", NULL};
        execv("/usr/sbin/nvram", (char **)&args_nvram);
        printf("Child should never reach here!\n");
        exit(-1);
    } else {
        printf("Unable to fork!\n");
    }
    
    locknvram();
    
    printf("APFS Snapshots: \n");
    printf("=========\n");
    int snapshot_success = list_snapshots("/");
    printf("=========\n");
    
    ret = remountRootAsRW(slide, kern_proc, our_proc, snapshot_success);
    
    if (ret != 0) {
        err = ERR_ROOTFS_REMOUNT;
        goto out;
    }
    
    copy_basebinaries();
    
    forkAndRunMount = fork();
    if (forkAndRunMount > 0){
        waitpid(forkAndRunMount, NULL, 0);
    } else if (forkAndRunMount == 0){
        const char *args_mount[] = {"mount", NULL};
        execv("/sbin/mount", (char **)&args_mount);
        printf("Child should never reach here!\n");
        exit(-1);
    } else {
        printf("Unable to fork!\n");
    }
    
    dumpContentsOfDir("/");
    dumpContentsOfDir("/electra");
    
#define BinaryLocation "/electra/inject_criticald"
    
    int rv;
    pid_t pd;
    
    const char* args_amfid[] = {BinaryLocation, itoa(amfid_pid), "/electra/amfid_payload.dylib", NULL};
    rv = posix_spawn(&pd, BinaryLocation, NULL, NULL, (char **)&args_amfid, NULL);
    waitpid(pd, NULL, 0);
    
    unlink("/.amfid_success");
    const char *args_helloworld[] = {"helloworld", NULL};
    rv = posix_spawn(&pd, "/electra/helloworld", NULL, NULL, (char **)&args_helloworld, NULL);
    waitpid(pd, NULL, 0);
    
    if (!file_exists("/.amfid_success")){
        printf("amfid injection fail!\n");
        return ERR_AMFID_PATCH;
    }
    unlink("/.amfid_success");
    
    int bootstrapped = open("/.bootstrapped_electra", O_RDONLY);
    
    if (bootstrapped == -1) {
        if (checkLiberiOS() == true) {
            removingLiberiOS();
            removeLiberiOS();
        }
#if ELECTRADEBUG
#else /* !ELECTRADEBUG */
        if (detectConflictJailbreak()) {
            err = ERR_CONFLICT;
            goto out;
        }
#endif /* !ELECTRADEBUG */
        removePotentialJailbreak();
    }
    
    if (snapshot_success == 0 && kCFCoreFoundationVersionNumber <= 1451.51){
        if (bootstrapped == -1) {
            int snapshot = check_snapshot("/", "electra-prejailbreak");
            if (snapshot == 1){
                printf("Snapshot exists!\n");
            } else if (snapshot == 0){
                rename("/electra/createSnapshot", "/createSnapshot");
                pid_t pd;
                rmdir("/electra");
                
                rv = posix_spawn(&pd, "/createSnapshot", NULL, NULL, (char **)&(const char*[]){ "createSnapshot", NULL }, NULL);
                waitpid(pd, NULL, 0);
                
                printf("APFS Snapshots: \n");
                
                printf("=========\n");
                list_snapshots("/");
                printf("=========\n");
                
                snapshot = check_snapshot("/", "electra-prejailbreak");
                if (snapshot != 1){
                    err = ERR_SNAPSHOT;
                    goto out;
                }
                
                acknowledgeSnapshotWarning = false;
                displaySnapshotNotice();
                while (!acknowledgeSnapshotWarning){
                    usleep(100);
                }
                
                copy_basebinaries();
            }
        } else {
            int snapshot = check_snapshot("/", "electra-prejailbreak");
            if (snapshot != 1){
                if (!file_exists("/.electra_no_snapshot")){
                    acknowledgeSnapshotWarning = false;
                    displaySnapshotWarning();
                    while (!acknowledgeSnapshotWarning){
                        usleep(100);
                    }
                    int rv = open("/.electra_no_snapshot", O_RDWR|O_CREAT);
                    close(rv);
                }
            }
        }
    }
    
    close(bootstrapped);
    
    printf("Running mount...\n");
    
    term_kexecute();
    
    extract_bootstrap();
    
    printf("Starting server...\n");
    start_jailbreakd(kernel_base);
    
    while (!file_exists("/var/run/jailbreakd.pid")){
        printf("Waiting for jailbreakd...\n");
        usleep(100000); //100 ms
    }
    
    unlink("/electra/createSnapshot");
    unlink("/electra/rm");
    
    update_springboard_plist();
    
    kill(cfprefsd_pid, SIGKILL);
    
    term_kernel();
    
    if (enable_tweaks) {
        run(""BinaryLocation" 1 /electra/pspawn_payload.dylib");
    }
    
    getAndSendStats();
    
    if (enable_tweaks){
        startDaemons();
        
        sleep(2);
        
        run("/usr/bin/ldrestart");
        
    }
    return err;
out:
    term_kexecute();
    term_kernel();
    return err;
}
